package controller

import (
	"VideoManageSystem/internal/initConfig"
	"VideoManageSystem/internal/model"
	"VideoManageSystem/internal/service"
	"VideoManageSystem/pkg/utils"
	"bytes"
	"fmt"
	"io"
	"mime/multipart"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"sync"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/google/uuid"
	"go.uber.org/zap"
)

type ChunkUploadInfo struct {
	FileName       string       // 文件名
	FileExt        string       // 文件后缀
	Uploader       string       // 上传者
	Size           string       // 文件大小
	Content        string       // 内容
	RecordTime     string       // 拍摄时间
	UploadTime     string       // 上传时间
	TotalChunks    int          // 总分片数
	ReceivedChunks map[int]bool // 接收到的分片
}

// 用于存储分片上传的临时信息
var (
	chunkUploads = make(map[string]*ChunkUploadInfo)
	chunkMutex   sync.RWMutex
)

func UploadVideoChunk(c *gin.Context) {
	appConfig := initConfig.GetAppConfig()
	if appConfig == nil {
		c.JSON(500, gin.H{
			"code":    500,
			"message": "配置未加载",
		})
		return
	}

	var userInput struct {
		ChunkNumber string `form:"chunkNumber"` // 分片下标 从1开始
		TotalChunks string `form:"totalChunks"` // 总分片数
		Identifier  string `form:"identifier"`  // 视频标识，同一视频的不同分片，标识相同
		FileName    string `form:"fileName"`    // 文件名 不需要写后缀名
		Uploader    string `form:"uploader"`    // 上传者
		Size        string `form:"size"`        // 文件大小
		Content     string `form:"content"`     // 文件内容
		RecordTime  string `form:"recordTime"`  // 拍摄时间
		UploadTime  string `form:"uploadTime"`  // 上传时间
	}

	if err := c.ShouldBindWith(&userInput, binding.FormMultipart); err != nil {
		c.JSON(400, gin.H{
			"code":        400,
			"message":     "请求参数错误；" + err.Error(),
			"chunkNumber": userInput.ChunkNumber,
		})
		return
	}

	// 获取上传的文件
	file, header, err := c.Request.FormFile("file")
	if err != nil {
		zap.L().Error("获取文件失败", zap.Error(err))
		c.JSON(400, gin.H{
			"code":        400,
			"message":     "无法获取上传的文件；错误：" + err.Error(),
			"chunkNumber": userInput.ChunkNumber,
		})
		return
	}
	defer func(file multipart.File) {
		err := file.Close()
		if err != nil {
			zap.L().Error("无法关闭上传的分片文件", zap.String("分片下标：", userInput.ChunkNumber), zap.Error(err))
		}
	}(file)

	chunkNumber, _ := strconv.Atoi(userInput.ChunkNumber)
	totalChunks, _ := strconv.Atoi(userInput.TotalChunks)
	fileExt := strings.TrimPrefix(filepath.Ext(header.Filename), ".")

	// 清空temp目录
	if chunkNumber == 1 {
		tempDir1 := filepath.Join(appConfig.Video.StoragePath, "temp")
		if err := os.RemoveAll(tempDir1); err != nil {
			zap.L().Error("删除临时目录失败", zap.String("dir", tempDir1), zap.Error(err))
		}
	}

	// 创建临时目录
	tempDir := filepath.Join(appConfig.Video.StoragePath, "temp", userInput.Identifier)
	if err := os.MkdirAll(tempDir, os.ModePerm); err != nil {
		zap.L().Error("创建临时目录失败", zap.Error(err))
		c.JSON(500, gin.H{
			"code":        500,
			"message":     "无法创建临时目录；错误：" + err.Error(),
			"chunkNumber": userInput.ChunkNumber,
		})
		return
	}

	// 保存分片
	chunkPath := filepath.Join(tempDir, fmt.Sprintf("chunk_%d", chunkNumber))
	chunkFile, err := os.Create(chunkPath)
	if err != nil {
		zap.L().Error("创建分片文件失败", zap.String("分片下标", userInput.ChunkNumber), zap.Error(err))
		c.JSON(500, gin.H{
			"code":        500,
			"message":     "无法创建分片文件；错误：" + err.Error(),
			"chunkNumber": userInput.ChunkNumber,
		})
		return
	}
	defer func(chunkFile *os.File) {
		err := chunkFile.Close()
		if err != nil {
			zap.L().Error("无法关闭创建的分片文件", zap.String("分片下标：", userInput.ChunkNumber), zap.Error(err))
		}
	}(chunkFile)

	if _, err = io.Copy(chunkFile, file); err != nil {
		zap.L().Error("保存分片失败", zap.String("分片下标：", userInput.ChunkNumber), zap.Error(err))
		c.JSON(500, gin.H{
			"code":        500,
			"message":     "保存分片失败；错误：" + err.Error(),
			"chunkNumber": userInput.ChunkNumber,
		})
		return
	}

	// 更新分片上传信息
	chunkMutex.Lock()
	if _, exists := chunkUploads[userInput.Identifier]; !exists {
		chunkUploads[userInput.Identifier] = &ChunkUploadInfo{
			TotalChunks:    totalChunks,
			ReceivedChunks: make(map[int]bool),
			FileName:       userInput.FileName,
			FileExt:        fileExt,
			Uploader:       userInput.Uploader,
			Size:           userInput.Size,
			Content:        userInput.Content,
			RecordTime:     userInput.RecordTime,
			UploadTime:     userInput.UploadTime,
		}
	}
	chunkUploads[userInput.Identifier].ReceivedChunks[chunkNumber] = true
	chunkMutex.Unlock()

	// 检查是否所有分片都已上传
	// 读锁
	chunkMutex.RLock()
	uploadInfo := chunkUploads[userInput.Identifier]
	chunkMutex.RUnlock()

	if len(uploadInfo.ReceivedChunks) == totalChunks {
		// 所有分片都已上传，合并文件
		videoID, videoURL, coverURL, err := mergeChunks(userInput.Identifier, appConfig)
		if err != nil {
			c.JSON(500, gin.H{
				"code":    500,
				"message": "分片合并失败" + err.Error(),
			})
			return
		}

		//// ========== 新增：调用 VA 服务并保存元数据 ==========
		//vaReqBody := map[string]interface{}{
		//	"action":        "VA:ADD_VIDEO",
		//	"videoId":       videoID,
		//	"videoFilePath": videoFilePath,
		//}
		//
		//jsonData, _ := json.Marshal(vaReqBody)
		//resp, err := http.Post("http://127.0.0.1:6590/api/va/videos", "application/json", bytes.NewBuffer(jsonData))
		//if err != nil {
		//	zap.L().Error("调用 VA 服务失败", zap.Error(err))
		//	c.JSON(500, gin.H{
		//		"code":    500,
		//		"message": "调用视频分析服务失败: " + err.Error(),
		//	})
		//	return
		//}
		//defer resp.Body.Close()
		//
		//if resp.StatusCode != http.StatusOK {
		//	body, _ := io.ReadAll(resp.Body)
		//	zap.L().Error("VA 服务返回非200状态", zap.Int("status", resp.StatusCode), zap.ByteString("body", body))
		//	c.JSON(500, gin.H{
		//		"code":    500,
		//		"message": fmt.Sprintf("视频分析服务异常，状态码：%d", resp.StatusCode),
		//	})
		//	return
		//}
		//
		//var vaResp struct {
		//	Success bool   `json:"success"`
		//	Message string `json:"message"`
		//	Data    struct {
		//		Width      int     `json:"width"`
		//		Height     int     `json:"height"`
		//		Fps        float64 `json:"fps"` // 注意：Go 中 float64 对应 JSON number
		//		Duration   int     `json:"duration"`
		//		FileType   string  `json:"fileType"`
		//		FileSize   int64   `json:"fileSize"`
		//		CreateTime int64   `json:"createTime"`
		//		ModifyTime int64   `json:"modifyTime"`
		//		MD5        string  `json:"md5"`
		//	} `json:"data"`
		//}
		//
		//if err := json.NewDecoder(resp.Body).Decode(&vaResp); err != nil {
		//	zap.L().Error("解析 VA 响应失败", zap.Error(err))
		//	c.JSON(500, gin.H{
		//		"code":    500,
		//		"message": "无法解析视频分析服务响应: " + err.Error(),
		//	})
		//	return
		//}
		//
		//if !vaResp.Success {
		//	zap.L().Error("VA 服务业务失败", zap.String("message", vaResp.Message))
		//	c.JSON(500, gin.H{
		//		"code":    500,
		//		"message": "视频分析失败: " + vaResp.Message,
		//	})
		//	return
		//}
		//
		//// 插入 video_metadata 表
		//metadata := map[string]interface{}{
		//	"video_id":    videoID,
		//	"width":       vaResp.Data.Width,
		//	"height":      vaResp.Data.Height,
		//	"fps":         vaResp.Data.Fps,
		//	"duration":    vaResp.Data.Duration,
		//	"file_type":   vaResp.Data.FileType,
		//	"file_size":   vaResp.Data.FileSize,
		//	"create_time": vaResp.Data.CreateTime,
		//	"modify_time": vaResp.Data.ModifyTime,
		//	"md5":         vaResp.Data.MD5,
		//}
		//
		//result := initConfig.GormDb.Table("video_metadata").Create(metadata)
		//if result.Error != nil {
		//	zap.L().Error("保存视频元数据失败", zap.Error(result.Error))
		//	c.JSON(500, gin.H{
		//		"code":    500,
		//		"message": "保存视频元数据失败: " + result.Error.Error(),
		//	})
		//	return
		//}
		//// ========== 新增逻辑结束 ==========

		// 返回 JSON 响应
		c.JSON(200, gin.H{
			"code":     200,
			"message":  "视频上传成功",
			"id":       videoID,
			"videoUrl": videoURL,
			"coverUrl": coverURL,
		})
		return
	}

	c.JSON(200, gin.H{
		"code":        200,
		"message":     "分片上传成功",
		"chunkNumber": userInput.ChunkNumber,
	})
}

// mergeChunks 合并分片、提取封面、转码、移动最终文件
func mergeChunks(identifier string, appConfig *initConfig.AppConfig) (string, string, string, error) {
	chunkMutex.RLock()
	uploadInfo := chunkUploads[identifier]
	chunkMutex.RUnlock()

	tempDir := filepath.Join(appConfig.Video.StoragePath, "temp", identifier)
	videoDir := filepath.Join(appConfig.Video.StoragePath, uploadInfo.FileName)

	// 创建视频目录
	if err := os.MkdirAll(videoDir, os.ModePerm); err != nil {
		zap.L().Error("创建视频目录失败", zap.Error(err))
		return "", "", "", err
	}

	// 合并所有分片为 FileName.mp4
	videoFilePath := filepath.Join(videoDir, fmt.Sprintf("%s.mp4", uploadInfo.FileName))
	videoFile, err := os.Create(videoFilePath)
	if err != nil {
		zap.L().Error("创建完整视频文件失败", zap.Error(err))
		return "", "", "", err
	}
	defer func(videoFile *os.File) {
		err := videoFile.Close()
		if err != nil {
			zap.L().Error("无法关闭视频文件", zap.Error(err))
		}
	}(videoFile)

	for i := 1; i <= uploadInfo.TotalChunks; i++ {
		chunkPath := filepath.Join(tempDir, fmt.Sprintf("chunk_%d", i))

		// 先检查文件是否存在
		if _, err := os.Stat(chunkPath); os.IsNotExist(err) {
			zap.L().Error("分片文件不存在", zap.Int("chunkNumber", i), zap.String("path", chunkPath))
			return "", "", "", fmt.Errorf("分片 %d 不存在", i)
		}

		// 检查文件大小是否为 0
		info, _ := os.Stat(chunkPath)
		if info.Size() == 0 {
			zap.L().Error("分片文件为空", zap.Int("chunkNumber", i), zap.String("path", chunkPath))
			return "", "", "", fmt.Errorf("分片 %d 为空", i)
		}

		chunkFile, err := os.Open(chunkPath)
		if err != nil {
			zap.L().Error("打开分片文件失败", zap.Int("chunkNumber", i), zap.Error(err))
			return "", "", "", err
		}

		if _, err := io.Copy(videoFile, chunkFile); err != nil {
			zap.L().Error("复制分片数据失败", zap.Int("chunkNumber", i), zap.Error(err))
			_ = chunkFile.Close()
			return "", "", "", err
		}
		_ = chunkFile.Close()
	}

	// 在 for 循环结束后，先关闭文件
	if err := videoFile.Close(); err != nil {
		zap.L().Error("关闭视频文件失败", zap.Error(err))
		return "", "", "", err
	}

	// 检查合并后的文件是否有效
	finalFileInfo, err := os.Stat(videoFilePath)
	if err != nil {
		zap.L().Error("无法获取合并后视频文件信息", zap.Error(err))
		return "", "", "", err
	}

	if finalFileInfo.Size() == 0 {
		zap.L().Error("合并后的视频文件大小为 0")
		return "", "", "", fmt.Errorf("合并后的视频文件为空")
	}

	zap.L().Info("所有分片已成功合并为完整视频",
		zap.String("videoFilePath", videoFilePath),
		zap.Int64("fileSize", finalFileInfo.Size()),
	)

	// 获取旋转角度并转换为 .ts 文件
	angle, err := getVideoRotation(videoFilePath)
	if err != nil {
		zap.L().Error("获取视频旋转角度失败", zap.Error(err))
		return "", "", "", err
	}

	tsFileDir := filepath.Join(tempDir, "tsfile")
	if err := os.MkdirAll(tsFileDir, os.ModePerm); err != nil {
		zap.L().Error("创建ts文件目录失败", zap.Error(err))
		return "", "", "", err
	}

	tsFilePath := filepath.Join(tsFileDir, "video.ts")

	args := []string{
		"-i", videoFilePath,
		"-loglevel", "quiet",
		"-c:v", "libx264",
		"-c:a", "aac",
		"-b:a", "128k",
		"-preset", "fast",
		"-crf", "23",
	}

	switch angle {
	case 90:
		args = append(args, "-vf", "transpose=1")
	case 180:
		args = append(args, "-vf", "transpose=2,transpose=2")
	case 270:
		args = append(args, "-vf", "transpose=2")
	case -90, 70, 280:
		args = append(args, "-vf", "transpose=2")
	}

	args = append(args,
		"-bsf:v", "h264_mp4toannexb",
		"-f", "mpegts",
		tsFilePath,
	)

	cmd := exec.Command("ffmpeg", args...)
	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &out

	if err := cmd.Run(); err != nil {
		zap.L().Error("FFmpeg 转换失败", zap.Error(err), zap.String("output", out.String()))
		return "", "", "", err
	}

	zap.L().Info("视频转码为 .ts 成功", zap.String("tsPath", tsFilePath))

	// 提取首帧：使用副本处理以避免文件锁
	coverImagePath := filepath.Join(videoDir, fmt.Sprintf("%s.jpg", uploadInfo.FileName))
	copiedFinalForCover := filepath.Join(tempDir, "final_for_cover.mp4")

	if err := copyFile(videoFilePath, copiedFinalForCover); err != nil {
		zap.L().Error("复制视频副本用于提取封面失败", zap.Error(err))
		return "", "", "", err
	}

	if err := utils.ExtractFirstFrame(copiedFinalForCover, coverImagePath); err != nil {
		zap.L().Error("提取视频首帧失败", zap.Error(err))
		_ = os.Remove(copiedFinalForCover)
		return "", "", "", err
	}
	_ = os.Remove(copiedFinalForCover)

	// 构建文件名称
	videoFileName := fmt.Sprintf("%s.mp4", uploadInfo.FileName)
	//videoFileName := fmt.Sprintf("%s.%s", uploadInfo.FileName, uploadInfo.FileExt)

	// 构造访问 URL
	externalAddress := appConfig.Video.ExternalAddress
	videoURL := fmt.Sprintf("%s/videos/%s/%s", strings.TrimSuffix(externalAddress, "/"), uploadInfo.FileName, videoFileName)
	coverURL := fmt.Sprintf("%s/videos/%s/%s.jpg", strings.TrimSuffix(externalAddress, "/"), uploadInfo.FileName, uploadInfo.FileName)

	// 保存视频信息到数据库
	newVideo := model.Video{
		ID:         uuid.New().String(),
		VideoURL:   videoURL,
		CoverURL:   coverURL,
		Size:       uploadInfo.Size,
		Name:       uploadInfo.FileName,
		Content:    uploadInfo.Content,
		Uploader:   uploadInfo.Uploader,
		RecordTime: uploadInfo.RecordTime,
		UploadTime: uploadInfo.UploadTime,
		FilePath:   videoFilePath,
	}

	if err := service.SaveVideo(newVideo); err != nil {
		zap.L().Error("保存视频信息失败", zap.Error(err))
		return "", "", "", err
	}

	// 清理临时数据
	chunkMutex.Lock()
	delete(chunkUploads, identifier)
	chunkMutex.Unlock()

	zap.L().Info("视频上传合并并处理完成",
		zap.String("name", uploadInfo.FileName),
		zap.String("size", uploadInfo.Size),
		zap.String("uploader", uploadInfo.Uploader),
		zap.String("recordTime", uploadInfo.RecordTime),
		zap.String("uploadTime", uploadInfo.UploadTime),
		zap.String("videoPath", videoFilePath),
		zap.String("coverPath", coverImagePath),
		zap.String("videoUrl", videoURL),
		zap.String("coverUrl", coverURL),
	)

	return newVideo.ID, newVideo.VideoURL, newVideo.CoverURL, nil
}

// getVideoRotation 获取视频的旋转角度（如 90, 180, 270）
func getVideoRotation(path string) (int, error) {
	// 先检查文件是否存在
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return 0, fmt.Errorf("文件不存在: %s", path)
	}

	// 再检查文件大小是否为 0
	fileInfo, _ := os.Stat(path)
	if fileInfo.Size() == 0 {
		return 0, fmt.Errorf("文件大小为 0，无法解析 rotation")
	}

	cmd := exec.Command("ffprobe",
		"-v", "error",
		"-select_streams", "v:0",
		"-show_entries", "stream=rotation",
		"-of", "default=nw=1",
		path,
	)

	out, err := cmd.CombinedOutput()
	if err != nil {
		return 0, fmt.Errorf("执行 ffprobe 失败: %v, 输出: %s", err, string(out))
	}

	str := strings.TrimSpace(string(out))
	if str == "" {
		return 0, nil
	}

	angle, err := strconv.Atoi(strings.TrimPrefix(str, "rotation="))
	if err != nil {
		return 0, fmt.Errorf("解析 rotation 元数据失败: %v", err)
	}

	return angle, nil
}

// copyFile 将 src 文件复制到 dst 路径
func copyFile(src, dst string) error {
	input, err := os.ReadFile(src)
	if err != nil {
		return err
	}
	return os.WriteFile(dst, input, 0644)
}
